<?php

/**
 * Created by PhpStorm.
 * Author: aaasayok
 * Email: aaasayok@gmail.com
 * Date: 2017/1/8-21:41
 */

namespace WebSocket;

class WebSocket
{
    /**
     * @var resource 连接server的client
     */
    protected $master;

    /**
     * @var array 不同状态的socket管理
     */
    protected $sockets;

    /**
     * @var bool 判断是否握手
     */
    protected $handShakes = [];


    public function __construct($address = '127.0.0.1', $port = 80)
    {
        $this->master = $this->createSocket($address, $port);

        $this->sockets = [$this->master];
    }

    /**
     * 建立套接字
     *
     * @param $address
     * @param $port
     * @return resource
     */
    private function createSocket($address, $port)
    {
        try {
            $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

            socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);

            socket_bind($socket, $address, $port);

            socket_listen($socket);

            $this->dump('create socket success...');

            return $socket;

        } catch (\Exception $e) {
            $this->dd($e);
        }
    }


    public function run()
    {
        while (true) {
            $sockets = $this->sockets;

            $write = $except = null;
            socket_select($sockets, $write, $except, null);
            foreach ($sockets as $k => $socket) {
                if ($socket == $this->master) {
                    $client = socket_accept($this->master);

                    if ($client < 0) {
                        $this->dump('socket accept failed');
                        continue;
                    } else {
                        array_push($this->sockets, $client);
                        $this->handShakes[(int) $client] = false;
                        //$this->sockets[] = $client;
                        $this->dump('connect client');
                    }

                }  else {

                    $bytes = socket_recv($socket, $buffer, 2048, 0);
                    $this->dump('bytes:' . $bytes);
                    if ($bytes == 0) {
                        unset($this->sockets[$k]);
                        continue;
                    }

                    if (!$this->handShakes[(int) $socket]) {
                        $this->doHandShake($socket, $buffer);
                    } else {
                        //处理数据帧
                        $buffer = $this->decode($buffer);

                        //发送数据
                        $this->send($socket, $buffer);
                    }
                }
            }
        }

    }

    /***
     * 发送数据
     *
     * @param $socket
     * @param $msg
     */
    public function send($socket, $msg)
    {
        $return = [
            'socket' => (int) $socket,
            'name'   => 'server',
            'msg'    => $msg,
            'status' => 1,
        ];

        $msg = $this->frame(json_encode($return));

        socket_write($socket, $msg, strlen($msg));
    }

    /**
     * 握手
     *
     * @param $socket
     * @param $request
     * @return bool
     */
    protected function doHandShake($socket, $request)
    {
        $requestKey = $this->getKey($request);
        $acceptKey  = $this->encrypt($requestKey);

        $response = "HTTP/1.1 101 Switching Protocols\r\n" .
            "Upgrade: websocket\r\n" .
            "Connection: Upgrade\r\n" .
            "Sec-WebSocket-Accept: " . $acceptKey . "\r\n" .
            "\r\n";

        if (false !== $ret = socket_write($socket, $response)) {
            $this->handShakes[(int) $socket] = true;
            $this->dump('shakeHands success...');
            return true;
        }

        $this->dump('shakeHands fail...');

        return false;
    }

    /**
     * 解析数据帧
     *
     * @param $buffer
     * @return null|string
     */
    protected function decode($buffer)
    {
        $len = $masks = $data = $decoded = null;

        $len = ord($buffer[1]) & 127;

        switch ($len) {
            case 126:
                $masks = substr($buffer, 4, 4);
                $data  = substr($buffer, 8);
                break;
            case 127:
                $masks = substr($buffer, 10, 4);
                $data  = substr($buffer, 14);
                break;
            default:
                $masks = substr($buffer, 2, 4);
                $data  = substr($buffer, 6);
        }

        $dataLens = strlen($data);
        for ($index = 0; $index < $dataLens; $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }

        return $decoded;
    }

    /**
     * 返回帧信息处理
     *
     * @param $s
     * @return string
     */
    protected function frame($s)
    {
        $a = str_split($s, 125);
        if (count($a) == 1) {
            return "\x81" . chr(strlen($a[0])) . $a[0];
        }
        $ns = "";
        foreach ($a as $o) {
            $ns .= "\x81" . chr(strlen($o)) . $o;
        }
        return $ns;
    }

    /**
     * parse the frame
     *
     * @param $d
     * @return array
     */
    private function parseFrame($d){
        $firstByte = ord(substr($d,0,1));
        $result =array();
        $result['FIN'] = $this->getBit($firstByte,1);
        $result['opcode'] = $firstByte & 15;
        $result['length'] = ord(substr($d,1,1)) & 127;
        return $result;
    }

    private function getBit($m,$n){
         return  ($n >> ($m-1)) & 1;
    }

    protected function encrypt($key)
    {
        return base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
    }

    /**
     *获取 webSocket 头部key
     *
     * @param $request
     * @return string
     */
    protected function getKey($request)
    {
        $key = '';
        if (preg_match('/Sec-WebSocket-Key:(.*)\r\n/', $request, $match)) {
            $key = $match[1];
        }

        return trim($key);
    }

    protected function dump($message)
    {
        echo "<pre>";
        var_dump($message);
    }

    protected function dd($message)
    {
        $this->dump($message);
        die;
    }


}